Release: CalDAV sync bundle hardening and LMW sync

This commit is contained in:
Jürgen Mummert
2026-03-27 22:16:48 +01:00
commit c6f63a56a9
36 changed files with 2993 additions and 0 deletions
+130
View File
@@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
namespace Mummert\CalDavSyncBundle\CalDav\Remote;
use Mummert\CalDavSyncBundle\CalDav\Parser\CalDavXmlParser;
use Mummert\CalDavSyncBundle\CalDav\Parser\ICalendarParser;
use Mummert\CalDavSyncBundle\CalDav\Transport\CalDavTransportInterface;
use Mummert\CalDavSyncBundle\Config\CalendarSyncConfig;
use Mummert\CalDavSyncBundle\Sync\RemoteEvent;
use Psr\Log\LoggerInterface;
final readonly class RemoteCalendarReader
{
public function __construct(
private CalDavTransportInterface $transport,
private CalDavXmlParser $xmlParser,
private ICalendarParser $icalendarParser,
private LoggerInterface $logger,
) {
}
/**
* @return list<RemoteEvent>
*/
public function readEvents(CalendarSyncConfig $config): array
{
$response = $this->transport->report(
$config->caldavUrl,
$config->caldavUsername,
$config->caldavPassword,
$this->buildCalendarQueryBody(),
1,
['Content-Type' => 'application/xml; charset=utf-8'],
);
if (!in_array($response->statusCode, [200, 207], true)) {
$this->logger->warning('CalDAV calendar query failed.', [
'calendarId' => $config->calendarId,
'url' => $config->caldavUrl,
'statusCode' => $response->statusCode,
]);
return [];
}
$parsedItems = $this->xmlParser->parseCalendarMultistatus($response->body);
$events = [];
foreach ($parsedItems as $item) {
$event = $this->icalendarParser->parseEvent(
$item['href'],
$item['etag'],
$item['calendarData'],
$config->timezoneOrDefault(),
);
if (null !== $event) {
$events[] = $event;
}
}
return $events;
}
public function readEventByHref(CalendarSyncConfig $config, string $href): ?RemoteEvent
{
$response = $this->transport->get(
$this->absoluteUrl($config->caldavUrl, $href),
$config->caldavUsername,
$config->caldavPassword,
['Accept' => 'text/calendar'],
);
if (200 !== $response->statusCode) {
$this->logger->warning('CalDAV GET by href failed.', [
'calendarId' => $config->calendarId,
'url' => $href,
'statusCode' => $response->statusCode,
]);
return null;
}
return $this->icalendarParser->parseEvent(
$href,
trim((string) $response->header('etag'), '"'),
$response->body,
$config->timezoneOrDefault(),
);
}
private function buildCalendarQueryBody(): string
{
return <<<'XML'
<?xml version="1.0" encoding="utf-8" ?>
<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
<d:prop>
<d:getetag />
<c:calendar-data />
</d:prop>
<c:filter>
<c:comp-filter name="VCALENDAR">
<c:comp-filter name="VEVENT" />
</c:comp-filter>
</c:filter>
</c:calendar-query>
XML;
}
private function absoluteUrl(string $calendarUrl, string $href): string
{
if (str_starts_with($href, 'http://') || str_starts_with($href, 'https://')) {
return $href;
}
$base = parse_url($calendarUrl);
if (!is_array($base) || !isset($base['scheme'], $base['host'])) {
return $href;
}
$prefix = $base['scheme'].'://'.$base['host'];
if (isset($base['port'])) {
$prefix .= ':'.$base['port'];
}
return $prefix.$href;
}
}