Skip to content

Commit

Permalink
feat: implement Dav for groups (#6799)
Browse files Browse the repository at this point in the history
  • Loading branch information
asbiin authored Aug 23, 2023
1 parent 4ed4536 commit b9783c6
Show file tree
Hide file tree
Showing 54 changed files with 1,270 additions and 135 deletions.
8 changes: 8 additions & 0 deletions app/Domains/Contact/Dav/Exporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,12 @@ protected function escape(mixed $value): ?string

return ! empty($value) ? trim($value) : null;
}

/**
* Formats and returns a string for DAV Card/Cal.
*/
protected function formatValue(?string $value): ?string
{
return ! empty($value) ? str_replace('\;', ';', trim($value)) : null;
}
}
4 changes: 2 additions & 2 deletions app/Domains/Contact/Dav/Services/ImportVCard.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,15 +178,15 @@ private function processEntry(VCard $entry, string $vcard): array
];
}

return $this->processEntryContact($entry, $vcard);
return $this->processEntryCard($entry, $vcard);
}

/**
* Process entry importation.
*
* @return array<string,mixed>
*/
private function processEntryContact(VCard $entry, string $vcard): array
private function processEntryCard(VCard $entry, string $vcard): array
{
$result = $this->importEntry($entry);

Expand Down
21 changes: 8 additions & 13 deletions app/Domains/Contact/ManageContact/Dav/ImportContact.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ public function import(VCard $vcard, ?VCardResource $result): ?VCardResource
if ($this->context->external) {
$contact->distant_etag = Arr::get($this->context->data, 'etag');
$contact->distant_uri = $uri;
}

$contact->save();
$contact->save();
}

return $contact;
});
Expand Down Expand Up @@ -114,22 +114,17 @@ private function getExistingContact(VCard $vcard): ?Contact
*/
private function getContactData(?Contact $contact): array
{
$result = [
return [
'account_id' => $this->account()->id,
'vault_id' => $this->vault()->id,
'author_id' => $this->author()->id,
'first_name' => $contact ? $contact->first_name : null,
'last_name' => $contact ? $contact->last_name : null,
'middle_name' => $contact ? $contact->middle_name : null,
'nickname' => $contact ? $contact->nickname : null,
'contact_id' => optional($contact)->id,
'first_name' => optional($contact)->first_name,
'last_name' => optional($contact)->last_name,
'middle_name' => optional($contact)->middle_name,
'nickname' => optional($contact)->nickname,
'gender_id' => $contact ? $contact->gender_id : $this->getGender('O')->id,
];

if ($contact) {
$result['contact_id'] = $contact->id;
}

return $result;
}

/**
Expand Down
39 changes: 39 additions & 0 deletions app/Domains/Contact/ManageGroups/Dav/ExportKind.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace App\Domains\Contact\ManageGroups\Dav;

use App\Domains\Contact\Dav\Exporter;
use App\Domains\Contact\Dav\ExportVCardResource;
use App\Domains\Contact\Dav\Order;
use App\Models\Group;
use Sabre\VObject\Component\VCard;

/**
* @implements ExportVCardResource<Group>
*/
#[Order(1)]
class ExportKind extends Exporter implements ExportVCardResource
{
public function getType(): string
{
return Group::class;
}

/**
* @param Group $resource
*/
public function export($resource, VCard $vcard): void
{
$kind = collect($vcard->select('X-ADDRESSBOOKSERVER-KIND'))->first();

if ($kind) {
$vcard->remove($kind);
$vcard->add('X-ADDRESSBOOKSERVER-KIND', 'group');

return;
}

$vcard->remove('KIND');
$vcard->add('KIND', 'group');
}
}
57 changes: 57 additions & 0 deletions app/Domains/Contact/ManageGroups/Dav/ExportMembers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

namespace App\Domains\Contact\ManageGroups\Dav;

use App\Domains\Contact\Dav\Exporter;
use App\Domains\Contact\Dav\ExportVCardResource;
use App\Domains\Contact\Dav\Order;
use App\Models\Contact;
use App\Models\Group;
use Sabre\VObject\Component\VCard;

/**
* @implements ExportVCardResource<Group>
*/
#[Order(20)]
class ExportMembers extends Exporter implements ExportVCardResource
{
public function getType(): string
{
return Group::class;
}

/**
* @param Group $resource
*/
public function export($resource, VCard $vcard): void
{
$kind = collect($vcard->select('X-ADDRESSBOOKSERVER-KIND'))->first();

$this->exportType($resource, $vcard, $kind ? 'X-ADDRESSBOOKSERVER-MEMBER' : 'MEMBER');
}

private function exportType($resource, VCard $vcard, string $type): void
{
$contacts = $resource->contacts
->map(fn (Contact $contact): string => $contact->distant_uuid ?? $contact->id)
->sort();

$current = collect($vcard->select($type));
$members = $current
->map(fn ($member): string => $this->formatValue((string) $member));

// Add new members
foreach ($contacts as $contact) {
if (! $members->contains($contact)) {
$vcard->add($type, $contact);
}
}

// Remove old members
foreach ($current as $member) {
if (! $contacts->contains($this->formatValue((string) $member))) {
$vcard->remove($member);
}
}
}
}
36 changes: 36 additions & 0 deletions app/Domains/Contact/ManageGroups/Dav/ExportNames.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace App\Domains\Contact\ManageGroups\Dav;

use App\Domains\Contact\Dav\Exporter;
use App\Domains\Contact\Dav\ExportVCardResource;
use App\Domains\Contact\Dav\Order;
use App\Models\Group;
use Sabre\VObject\Component\VCard;

/**
* @implements ExportVCardResource<Group>
*/
#[Order(10)]
class ExportNames extends Exporter implements ExportVCardResource
{
public function getType(): string
{
return Group::class;
}

/**
* @param Group $resource
*/
public function export($resource, VCard $vcard): void
{
$vcard->remove('FN');
$vcard->remove('N');

$vcard->add('FN', $this->escape($resource->name));

$vcard->add('N', [
$this->escape($resource->name),
]);
}
}
30 changes: 30 additions & 0 deletions app/Domains/Contact/ManageGroups/Dav/ExportTimestamp.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace App\Domains\Contact\ManageGroups\Dav;

use App\Domains\Contact\Dav\ExportVCardResource;
use App\Domains\Contact\Dav\Order;
use App\Models\Group;
use Sabre\VObject\Component\VCard;

/**
* @implements ExportVCardResource<Group>
*/
#[Order(1000)]
class ExportTimestamp implements ExportVCardResource
{
public function getType(): string
{
return Group::class;
}

/**
* @param Group $resource
*/
public function export($resource, VCard $vcard): void
{
$vcard->remove('REV');

$vcard->REV = $resource->updated_at->format('Ymd\\THis\\Z');
}
}
150 changes: 150 additions & 0 deletions app/Domains/Contact/ManageGroups/Dav/ImportGroup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<?php

namespace App\Domains\Contact\ManageGroups\Dav;

use App\Domains\Contact\Dav\Importer;
use App\Domains\Contact\Dav\ImportVCardResource;
use App\Domains\Contact\Dav\Order;
use App\Domains\Contact\Dav\VCardResource;
use App\Domains\Contact\ManageGroups\Services\CreateGroup;
use App\Domains\Contact\ManageGroups\Services\UpdateGroup;
use App\Models\Group;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Arr;
use Sabre\VObject\Component\VCard;

#[Order(10)]
class ImportGroup extends Importer implements ImportVCardResource
{
/**
* Can import Group.
*/
public function can(VCard $vcard): bool
{
$kind = (string) $vcard->KIND;
if ($kind == null) {
$kind = (string) collect($vcard->select('X-ADDRESSBOOKSERVER-KIND'))->first();
}

return $kind === 'group';
}

/**
* Import group.
*/
public function import(VCard $vcard, ?VCardResource $result): ?VCardResource
{
$group = $this->getExistingGroup($vcard);

$data = $this->getGroupData($group);
$original = $data;

$data = $this->importUid($data, $vcard);
$data = $this->importNames($data, $vcard);

if ($group === null) {
$group = app(CreateGroup::class)->execute($data);
} elseif ($data !== $original) {
$group = app(UpdateGroup::class)->execute($data);
}

if ($this->context->external && $group->distant_uuid === null) {
$group->distant_uuid = $this->getUid($vcard);
$group->save();
}

return Group::withoutTimestamps(function () use ($group): Group {
$uri = Arr::get($this->context->data, 'uri');
if ($this->context->external) {
$group->distant_etag = Arr::get($this->context->data, 'etag');
$group->distant_uri = $uri;

$group->save();
}

return $group;
});
}

/**
* Get existing group.
*/
protected function getExistingGroup(VCard $vcard): ?Group
{
$group = null;

if (($uri = Arr::get($this->context->data, 'uri')) !== null) {
$group = Group::firstWhere([
'vault_id' => $this->vault()->id,
'distant_uri' => $uri,
]);
}

if ($group === null && ($groupId = $this->getUid($vcard)) !== null) {
$group = Group::firstWhere([
'vault_id' => $this->vault()->id,
'distant_uuid' => $groupId,
]);
}

if ($group !== null && $group->vault_id !== $this->vault()->id) {
throw new ModelNotFoundException();
}

return $group;
}

/**
* Get group data.
*/
private function getGroupData(?Group $group): array
{
return [
'account_id' => $this->account()->id,
'vault_id' => $this->vault()->id,
'author_id' => $this->author()->id,
'group_id' => optional($group)->id,
'name' => optional($group)->name,
];
}

/**
* Import names of the group.
*/
public function importNames(array $contactData, VCard $entry): array
{
if ($this->hasNameInN($entry)) {
$contactData = $this->importNameFromN($contactData, $entry);
} elseif ($this->hasFN($entry)) {
$contactData = $this->importNameFromFN($contactData, $entry);
} else {
throw new \LogicException('Check if you can import entry!');
}

return $contactData;
}

private function hasNameInN(VCard $entry): bool
{
return $entry->N !== null && ! empty(Arr::get($entry->N->getParts(), '0'));
}

private function hasFN(VCard $entry): bool
{
return ! empty((string) $entry->FN);
}

private function importNameFromN(array $contactData, VCard $entry): array
{
$contactData['name'] = $this->formatValue(Arr::get($entry->N->getParts(), '0'));

return $contactData;
}

private function importNameFromFN(array $contactData, VCard $entry): array
{
$contactData['name'] = (string) $entry->FN;

return $contactData;
}
}
Loading

0 comments on commit b9783c6

Please sign in to comment.