Page MenuHomePhorge

D5550.1774894708.diff
No OneTemporary

Authored By
Unknown
Size
8 KB
Referenced Files
None
Subscribers
None

D5550.1774894708.diff

diff --git a/src/app/Backends/DAV/CommonObject.php b/src/app/Backends/DAV/CommonObject.php
--- a/src/app/Backends/DAV/CommonObject.php
+++ b/src/app/Backends/DAV/CommonObject.php
@@ -48,6 +48,15 @@
return $object;
}
+ /**
+ * Make the item compatible with standards (and Cyrus DAV) by fixing
+ * obvious issues, if possible
+ */
+ public function repair(): void
+ {
+ // do nothing by default
+ }
+
/**
* Create string representation of the DAV object
*
diff --git a/src/app/Backends/DAV/Vevent.php b/src/app/Backends/DAV/Vevent.php
--- a/src/app/Backends/DAV/Vevent.php
+++ b/src/app/Backends/DAV/Vevent.php
@@ -289,6 +289,60 @@
}
}
+ /**
+ * Make the item compatible with standards (and Cyrus DAV) by fixing
+ * obvious issues, if possible
+ */
+ public function repair(): void
+ {
+ if (!$this->vobject || $this->vobject->name != 'VCALENDAR') {
+ return;
+ }
+
+ // TODO: We apply changes to $this->vobject. We should do the same with $this, but
+ // we can ignore that until we have support for it in __toString().
+
+ $selfType = strtoupper(class_basename(static::class));
+ $master = null;
+ $exceptions = [];
+
+ foreach ($this->vobject->getComponents() as $component) {
+ if ($component->name == $selfType) {
+ if (empty($master) && empty($component->{'RECURRENCE-ID'})) {
+ $master = $component;
+ } elseif ($this->uid && $this->uid == $component->UID && !empty($component->{'RECURRENCE-ID'})) {
+ $exceptions[] = $component;
+ }
+ }
+ }
+
+ // Event exceptions cannot change the organizer
+ // We reset it to the original organizer, and make the user an attendee with role=chair.
+ if ($master && !empty($exceptions)) {
+ foreach ($exceptions as $exception) {
+ if ($exception->ORGANIZER && (string) $exception->ORGANIZER != (string) $master->ORGANIZER) {
+ $chair = $exception->ORGANIZER;
+ $exception->ORGANIZER = $master->ORGANIZER;
+ $attendee = null;
+
+ foreach ($exception->ATTENDEE as $_attendee) {
+ if ((string) $_attendee == (string) $chair) {
+ $attendee = $_attendee;
+ break;
+ }
+ }
+
+ if (!$attendee) {
+ $attendee = $exception->add('ATTENDEE', (string) $chair, $chair->parameters());
+ $attendee['PARTSTAT'] = 'ACCEPTED';
+ }
+
+ $attendee['ROLE'] = 'CHAIR';
+ }
+ }
+ }
+ }
+
/**
* Create string representation of the DAV object (iCalendar)
*
@@ -300,6 +354,7 @@
// TODO we currently can only serialize a message back that we just read
throw new \Exception("Writing from properties is not implemented");
}
+
return Writer::write($this->vobject);
}
}
diff --git a/src/app/DataMigrator/Driver/DAV.php b/src/app/DataMigrator/Driver/DAV.php
--- a/src/app/DataMigrator/Driver/DAV.php
+++ b/src/app/DataMigrator/Driver/DAV.php
@@ -214,7 +214,9 @@
throw new \Exception("Failed to fetch DAV item for {$item->id}");
}
- // TODO: Do any content changes, e.g. organizer/attendee email migration
+ // Make the item compatible with standards (and Cyrus DAV) if possible
+ // We try to prevent the event to be not accepted by the DAV server as much as we can.
+ $result[0]->repair();
$content = (string) $result[0];
diff --git a/src/tests/Feature/DataMigrator/DAVTest.php b/src/tests/Feature/DataMigrator/DAVTest.php
--- a/src/tests/Feature/DataMigrator/DAVTest.php
+++ b/src/tests/Feature/DataMigrator/DAVTest.php
@@ -2,9 +2,14 @@
namespace Tests\Feature\DataMigrator;
+use App\Backends\DAV\Vevent;
use App\DataMigrator\Account;
+use App\DataMigrator\Driver\DAV;
use App\DataMigrator\Engine;
+use App\DataMigrator\Interface\Folder;
+use App\DataMigrator\Interface\Item;
use App\DataMigrator\Queue as MigratorQueue;
+use Illuminate\Support\Facades\Http;
use Tests\BackendsTrait;
use Tests\TestCase;
@@ -113,4 +118,47 @@
// TODO: Assert that unmodified objects aren't migrated again
}
+
+ /**
+ * Test fixing data in DAV migration
+ */
+ public function testDataRepair(): void
+ {
+ $uri = \config('services.dav.uri');
+ $uri = preg_replace('|^http|', 'dav', $uri);
+ $src = new Account(preg_replace('|://|', '://john%40kolab.org:simple123@', $uri));
+
+ // Create a fake object to fetch from a fake DAV server
+ $folder = Folder::fromArray([
+ 'fullname' => 'Calendar',
+ 'id' => '/test/0',
+ 'type' => Engine::TYPE_EVENT,
+ ]);
+
+ $item = Item::fromArray([
+ 'id' => '/test/1.ics',
+ 'folder' => $folder,
+ ]);
+
+ $engine = new Engine();
+
+ $report = file_get_contents(self::BASE_DIR . '/data/DAV/report1.xml');
+ Http::fake([
+ 'dav/test' => Http::response($report, 207, ['Content-Type' => 'application/xml; charset=utf-8']),
+ ]);
+
+ // Fetch the item from a fake DAV server
+ // Note: For simplicity we do not attempt to store the event into a DAV server
+ $driver = new DAV($src, $engine);
+ $driver->fetchItem($item);
+
+ $event = new Vevent();
+ $this->invokeMethod($event, 'fromIcal', [$item->content]);
+ $this->assertSame('jack@kolab.org', $event->organizer['email']);
+ $this->assertSame('jack@kolab.org', $event->exceptions[0]->organizer['email']);
+ $this->assertSame('john@kolab.org', $event->exceptions[0]->attendees[0]['email']);
+ $this->assertSame('CHAIR', $event->exceptions[0]->attendees[0]['role']);
+
+ // Note: More data repair tests should be placed in another location
+ }
}
diff --git a/src/tests/data/DAV/report1.xml b/src/tests/data/DAV/report1.xml
new file mode 100644
--- /dev/null
+++ b/src/tests/data/DAV/report1.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<d:multistatus xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
+ <d:response>
+ <d:href>/test/1.ics</d:href>
+ <d:propstat>
+ <d:prop>
+ <d:getetag>"2134-314"</d:getetag>
+ <c:calendar-data>BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Roundcube 1.5-git//Sabre VObject 4.5.4//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:Europe/Berlin
+BEGIN:STANDARD
+DTSTART:20231029T010000
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+TZNAME:CET
+END:STANDARD
+BEGIN:STANDARD
+DTSTART:20241027T010000
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+TZNAME:CET
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20240331T010000
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+TZNAME:CEST
+END:DAYLIGHT
+END:VTIMEZONE
+BEGIN:VEVENT
+UID:1
+DTSTAMP:20240709T124304Z
+CREATED:20240709T124304Z
+LAST-MODIFIED:20240709T124304Z
+DTSTART;TZID=Europe/Berlin:20240710T103000
+DTEND;TZID=Europe/Berlin:20240710T113000
+SUMMARY:Test Meeting
+LOCATION:Berlin
+RRULE:FREQ=WEEKLY;INTERVAL=1
+SEQUENCE:0
+ATTENDEE;CN=John;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPANT;CUTYPE=INDI
+ VIDUAL:mailto:john@kolab.org
+ATTENDEE;CN=Ned;PARTSTAT=TENTATIVE;ROLE=REQ-PARTICIPANT;CUTYPE=IND
+ IVIDUAL:mailto:ned@kolab.org
+ORGANIZER;CN=Jack:mailto:jack@kolab.org
+TRANSP:OPAQUE
+END:VEVENT
+BEGIN:VEVENT
+UID:1
+RECURRENCE-ID;TZID=Europe/Berlin:20240717T123000
+DTSTAMP:20240709T124304Z
+CREATED:20240709T124304Z
+LAST-MODIFIED:20240709T124304Z
+DTSTART;TZID=Europe/Berlin:20240717T123000
+DTEND;TZID=Europe/Berlin:20240717T133000
+SUMMARY:Test Meeting
+LOCATION:Berlin
+SEQUENCE:0
+TRANSP:OPAQUE
+ATTENDEE;CN=John;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;CUTYPE=INDIVIDUAL:mailto:john@kolab.org
+ATTENDEE;CN=Ned;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;CUTYPE=INDIVIDUAL;RSVP=TRUE:mailto:ned@kolab.org
+ORGANIZER;CN=John:mailto:john@kolab.org
+END:VEVENT
+END:VCALENDAR</c:calendar-data>
+ </d:prop>
+ <d:status>HTTP/1.1 200 OK</d:status>
+ </d:propstat>
+ </d:response>
+</d:multistatus>

File Metadata

Mime Type
text/plain
Expires
Mon, Mar 30, 6:18 PM (3 d, 14 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18818544
Default Alt Text
D5550.1774894708.diff (8 KB)

Event Timeline