-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement Dav for groups (#6799)
- Loading branch information
Showing
54 changed files
with
1,270 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.